#include "thread.h"
#include "fifo.h"
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <valgrind/valgrind.h>
#include "macros.h"

// Mettre thread_yield à la fin de thread_create (imp pour préemption)

struct thread * current =NULL;
struct thread * runq_head=NULL;
struct thread * runq_tail=NULL;
struct thread * waitq_head=NULL;
struct thread * waitq_tail=NULL;
int initialized_library = 0;

void thread_disp(void) {
  struct thread *t = runq_head;
  printf("Current = %p (%d)\n############### RUNQ\n", current, current->status);
  while(t) {
    printf("%p (%d), ", t, t->status);
    t = t->next;
  }
  printf("\n############### FIN RUNQ\n\n");
}


extern void thread_init() {
	  	struct thread *t = malloc(sizeof(struct thread)); /* ici on initialise le main comme un thread classique pour avor un current*/
	  	//printf("malloc %p\n", t);
	  	getcontext(&t->ctx);
	  	NEW_CURRENT(t)
	  	initialized_library=1;
}
	  

extern thread_t thread_self(void){
	//ne peut pas renvoyer NULL
	//il faut gérer le cas avec initialized_library==0 ici => factoriser dans une fonction
		  if(initialized_library!=1){
		struct thread * newthread =malloc(sizeof(struct thread));
		//printf("malloc :%p\n", newthread);
	  	thread_init(newthread);
	  }
  return current;
}

extern void  thread_call(void *(* func)(void *), void * funcarg){



 //current->retval = func(funcarg); 
  //current->status=TERMINATED;
  //thread_yield();

  //runq_tail->next=current;
  //current->previous=runq_tail;
  //runq_tail=current;
 
  //a appeler dans thread_create
  
  thread_exit(func(funcarg));
}

/* creer un nouveau thread qui va exécuter la fonction func avec l'argument funcarg.
 * renvoie 0 en cas de succès, -1 en cas d'erreur.
 */
extern int thread_create(thread_t *newthreadp, void *(*func)(void *), void *funcarg){
  struct thread * newthread =malloc(sizeof(struct thread));
  //printf("malloc %p\n", newthread);
  ucontext_t * uc = &newthread->ctx;
 
  getcontext(uc);
  uc->uc_stack.ss_size = 64*1024;
  uc->uc_stack.ss_sp = malloc(uc->uc_stack.ss_size); //ss_sp = pointeur de pile
  //printf("malloc  pile %p\n", uc->uc_stack.ss_sp);
  /* juste après l'allocation de la pile */
	newthread->valgrind_stackid = VALGRIND_STACK_REGISTER(newthread->ctx.uc_stack.ss_sp,                 newthread->ctx.uc_stack.ss_sp + newthread->ctx.uc_stack.ss_size);
/* stocker valgrind_stackid dans votre structure thread */

  if ( uc->uc_stack.ss_sp ==NULL)
    return -1;
    
  makecontext(uc, (void (*)(void))thread_call,2,(void (*)(void))func, funcarg);
  newthread->status=READY;

	  if(initialized_library!=1){
	  	thread_init();
	  	ADD_RUNQ(newthread)
	  }
	  else {
	    ADD_RUNQ(newthread)
	  }
 
	  
  *newthreadp=newthread;   	   
  thread_yield();
  return 0;
}


/* passer la main à un autre thread.
 */
extern int thread_yield(void){
  //printf("THREAD_YIELD\n");
struct thread *old_current = current;
//thread_disp();

struct thread *t = runq_head;

/*if(t && (t->status==TERMINATED) (t->status==WAITING && t->thread_wait->status!=TERMINATED))) {
  t = t->next;
}*/

	  	if(t) {

  // On sort t de la runq
 DELETE_FROM_RUNQ(t)
  
  // On change le statut de current (sauf si déjà terminé ou en attente)
  if(current->status==RUNNING) {
    current->status = READY;
  	 // On met current dans la runq
  	 ADD_RUNQ(current)
  } else if(current->status==WAITING || current->status==TERMINATED) {
  	 // On met current dans la waitq
  	 ADD_WAITQ(current)
  }
  
  // On met t dans current
  NEW_CURRENT(t)
    //  printf("Thread yield on change le contexte\n");  
  // On swap
  swapcontext(&old_current->ctx, &current->ctx);
  }
  else {
    printf("#### le prochain thread prêt à exécuter est NULL\n");
   // si on a aucun thread a ordonnancé et que le current est fini, on le free
   //sinon on fait rien car le current doit finir son exécution
	/*if (current->status == TERMINATED)
   {
   	FREE(current)
   }
  }*/

  return 0;
  }
}

/* attendre la fin d'exécution d'un thread.
 * la valeur renvoyée par le thread est placée dans *retval.
 * si retval est NULL, la valeur de retour est ignorée.
 */
extern int thread_join(struct thread * thread, void **retval){
  // Mise en attente
  // Reordonnancement (suspend)
  // if(retval != NULL) retval = thread->retval;
   // Mise en attente
  current->thread_wait=thread;
  current->status=WAITING;
  thread->waited_by=current;
  // On passe la main à l'ordonnanceur.

  // L'ordonnanceur nous rend la main, le retour est possible grâce à la sauvegarde précédente du contexte.
  //while(thread->status!=TERMINATED) {
    if (thread->status!=TERMINATED)
      thread_yield();
    //  }
    
  current->status=RUNNING;
  current->thread_wait=NULL;
  // Reordonnancement (suspend)
  if(retval != NULL)
  	*retval = thread->retval;
  
  DELETE_FROM_WAITQ(thread)
  FREE(thread)
  
  if (!runq_head && !runq_tail)
  {
  	initialized_library = 0;
  	FREE(current)
  }
  
  return 0;
}



/* terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 */
extern void __attribute__ ((__noreturn__)) thread_exit(void *retval)  {
  current->status=TERMINATED;
  current->retval = retval;
  if (current->waited_by!=NULL) //si notre thread est attendu par un autre
  {
  	DELETE_FROM_WAITQ(current->waited_by)
  	ADD_RUNQ(current->waited_by)
  	current->waited_by->status=READY;
  }
  thread_yield();
}

